package deploy

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"gddgame.cc/galaxy/core/plugins/database"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/system"
	"github.com/toolkits/file"
)

const (
	ProjectPath     string = "path"
	ProjectIdentity string = "identity"
)

const (
	GitFlowType      FlowType = "git"
	DatabaseFlowType FlowType = "database"
	CloudFlowType    FlowType = "cloud"
	DownloadFlowType FlowType = "download"
	PackageFlowType  FlowType = "package"
)

type FlowEvent string

var (
	FlowDefine = map[FlowType]Flow{}
)

func init() {
	// 操作数据库
	FlowDefine[DatabaseFlowType] = &DatabaseFlow{}
	// 上传到云端
	FlowDefine[CloudFlowType] = &CloudFlow{}
	// git下载
	FlowDefine[GitFlowType] = &GitFlow{}
	// 从主控机下载
	FlowDefine[DownloadFlowType] = &DownloadFlow{}
	// 打包操作
	FlowDefine[PackageFlowType] = &PackageFlow{}
}

type FlowWrap interface {
	Context() def.Context
	WorkDir() string
	Nodes() []string
	Env() []string
	GenerateConfig(path string)
	Process(process Process, result string, finish bool)
	Var(key string) string
	Replace(source string) string
}

type Flow interface {
	Run(wrap FlowWrap, params map[string]interface{}) error
	Recovery(wrap FlowWrap, params map[string]interface{}) error
}

type FlowSlice []Flow

type DatabaseFlow struct {
}

func (flow *DatabaseFlow) Run(wrap FlowWrap, params map[string]interface{}) error {
	source := utils.ToInt(params["source"])
	desc := utils.ToInt(params["desc"])
	if source == 0 || desc == 0 {
		return nil
	}
	remark := utils.ToString(params["remark"])
	wait := make(chan error)
	snapshotId, err := database.SaveEnvironment(wrap.Context(), desc, remark, func(err error) {
		wait <- err
	})
	if err != nil {
		return err
	}
	if err := <-wait; err != nil {
		return err
	}
	snapshotId, err = database.RecoveryEnvironment(wrap.Context(), source, snapshotId, false, func(err error) {
		wait <- err
	})
	if err := <-wait; err != nil {
		return err
	}
	params["snapshot"] = snapshotId
	return nil
}

func (flow *DatabaseFlow) Recovery(wrap FlowWrap, params map[string]interface{}) error {
	snapshotId := utils.ToInt(params["snapshot"])
	if snapshotId == 0 {
		return nil
	}
	desc := utils.ToInt(params["desc"])
	wait := make(chan error)
	snapshotId, err := database.RecoveryEnvironment(wrap.Context(), desc, snapshotId, false, func(err error) {
		wait <- err
	})
	if err != nil {
		return err
	}
	if err := <-wait; err != nil {
		return err
	}
	return nil
}

type CloudFlow struct {
}

// 上传到云端
func (flow *CloudFlow) Run(wrap FlowWrap, params map[string]interface{}) error {
	return nil
}

func (flow *CloudFlow) Recovery(wrap FlowWrap, params map[string]interface{}) error {
	return nil
}

type DownloadFlow struct {
}

// node string
// path string

func (flow *DownloadFlow) Run(wrap FlowWrap, params map[string]interface{}) error {
	node := utils.ToString(params["node"])
	path := utils.ToString(params["path"])
	node = wrap.Replace(node)
	path = wrap.Replace(path)
	isLocal := false
	var err error
	if node == "127.0.0.1" || node == "localhost" || node == "local" {
		isLocal = true
	} else {
		isLocal, err = system.IsLocalIP(node)
		if err != nil {
			return err
		}
	}
	projectPath := wrap.Var(ProjectPath)
	if !isLocal {
		err = execCommand(RootProjectPath, wrap.Env(), func(r string, finish bool) {
			wrap.Process("download", r, finish)
		}, "scp", node+":"+path, RootProjectPath+"/")
		_, fileName := filepath.Split(path)
		path = RootProjectPath + "/" + fileName
	}
	// 拷贝文件
	// 不存在：下载git
	command := ""
	if strings.HasPrefix(path, "zip") {
		command = "unzip " + path + " -d " + projectPath
	} else if strings.HasSuffix(path, "tar.gz") {
		err = execCommand(RootProjectPath, wrap.Env(), func(r string, finish bool) {
			wrap.Process("download", r, finish)
		}, "mkdir -p "+projectPath)
		command = "tar -zxf " + path + " -C " + projectPath
	} else if strings.HasSuffix(path, "tar") {
		err = execCommand(RootProjectPath, wrap.Env(), func(r string, finish bool) {
			wrap.Process("download", r, finish)
		}, "mkdir -p "+projectPath)
		command = "tar -xf " + path + " -C " + projectPath
	}
	err = execCommand(RootProjectPath, wrap.Env(), func(r string, finish bool) {
		wrap.Process("download", r, finish)
	}, command)

	return nil
}

func (flow *DownloadFlow) Recovery(wrap FlowWrap, params map[string]interface{}) error {
	return nil
}

type GitFlow struct {
}

func (flow *GitFlow) Run(wrap FlowWrap, params map[string]interface{}) error {
	var errR error
	if err := checkCommandExists("git"); err != nil {
		return err
	}

	git := utils.ToString(params["git"])
	branch := utils.ToString(params["branch"])
	identity := wrap.Var(ProjectIdentity)
	projectPath := wrap.Var(ProjectPath)
	// 判断项目地址是否存在
	if pathInfo, err := os.Stat(projectPath); err != nil {
		// 不存在：下载git
		errR = execCommand(RootProjectPath, wrap.Env(), func(r string, finish bool) {
			wrap.Process("git", r, finish)
		}, "git", "clone", git, identity)
	} else if !pathInfo.IsDir() {
		return errors.New(projectPath + " not dir")
	} else {
		// 存在：更新git
		errR = execCommand(projectPath, wrap.Env(), func(r string, finish bool) {
			wrap.Process("git", r, finish)
		}, "git", "pull")
	}
	if errR != nil {
		return errR
	}

	// 切换分支，切换
	// 切换tag
	checkout := utils.ToString(params["tag"])
	if checkout == "" {
		checkout = branch
	}
	errR = execCommand(projectPath, wrap.Env(), func(r string, finish bool) {
		wrap.Process("git", r, finish)
	}, "git", "checkout", checkout)

	if errR != nil {
		return errR
	}
	return nil
}

func (flow *GitFlow) Recovery(wrap FlowWrap, params map[string]interface{}) error {
	return nil
}

type PackageFlow struct {
}

// compile: string
// path: string
// upload: string
// run: string
func (flow *PackageFlow) Run(wrap FlowWrap, params map[string]interface{}) error {
	command := utils.ToString(params["compile"])
	if command != "" {
		command = wrap.Replace(command)
		// 执行命令
		err := execCommand(wrap.WorkDir(), wrap.Env(), func(r string, finish bool) {
			wrap.Process(Deploy, r, finish)
		}, "project compile "+command)

		if err != nil {
			return err
		}
	}
	path := utils.ToString(params["path"])
	wrap.GenerateConfig(path)
	// 判断project.ini文件位置，拷贝到最外面
	// package流程，执行download的Flow
	// project.ini会在打包中，存放在path目录下

	if !file.IsExist(wrap.WorkDir() + "/project.ini") {
		if file.IsExist(wrap.WorkDir() + path + "/project.ini") {
			input, err := ioutil.ReadFile(wrap.WorkDir() + path + "/project.ini")
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(wrap.WorkDir()+"/project.ini", input, 0644)
			if err != nil {
				return err
			}
		}
	}

	nodes := wrap.Nodes()
	for index, node := range nodes {
		if node == "127.0.0.1" || node == "localhost" || node == "local" {
			nodes[index] = "local"
		} else {
			isLocal, _ := system.IsLocalIP(node)
			if isLocal {
				nodes[index] = "local"
			}
		}
	}

	command = utils.ToString(params["upload"])
	if command != "" {
		command = wrap.Replace(command)
		// "echo", "\""+strings.Join(record.Nodes, " ")+"\" | "+record.Command
		// 执行命令
		err := execCommand(wrap.WorkDir(), wrap.Env(), func(r string, finish bool) {
			wrap.Process(Deploy, r, finish)
		}, "echo", "\""+strings.Join(nodes, " ")+"\" | project upload batch "+command+" -b")

		if err != nil {
			return err
		}
	}

	command = utils.ToString(params["run"])
	if command != "" {
		command = wrap.Replace(command)
		// "echo", "\""+strings.Join(record.Nodes, " ")+"\" | "+record.Command
		// 执行命令
		err := execCommand(wrap.WorkDir(), wrap.Env(), func(r string, finish bool) {
			wrap.Process(Deploy, r, finish)
		}, "echo", "\""+strings.Join(nodes, " ")+"\" | project exec batch "+command+" -b")

		if err != nil {
			return err
		}
	}
	return nil
}

func (flow *PackageFlow) Recovery(wrap FlowWrap, params map[string]interface{}) error {
	command := utils.ToString(params["upload"])
	command = wrap.Replace(command)

	nodes := wrap.Nodes()
	for index, node := range nodes {
		if node == "127.0.0.1" || node == "localhost" || node == "local" {
			nodes[index] = "local"
		} else {
			isLocal, _ := system.IsLocalIP(node)
			if isLocal {
				nodes[index] = "local"
			}
		}
	}

	err := execCommand(wrap.WorkDir(), wrap.Env(), func(r string, finish bool) {
		wrap.Process(Deploy, r, finish)
	}, "echo", "\""+strings.Join(nodes, " ")+"\" | project recovery batch "+command+" -b")
	if err != nil {
		return err
	}
	return nil
}
